﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using log4net;

namespace AccountingModel.BLO
{
    public class TransactionManager
    {
        private static readonly ILog Logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public static TransactionManager GetInstance()
        {
            return new TransactionManager();
        }
        /// <summary>
        /// Kiểm tra tính hợp lệ của một transaction.
        /// - Phải có phần detail.
        /// - Tổng số debits và credits phải bằng nhau.
        /// </summary>
        /// <param name="transaction">chưa thông tin về một transaction cần kiểm tra</param>
        /// <returns>true nếu thông tin hợp lệ, false nếu không</returns>
        private static bool isTransactionValid(DTO.DTOTransaction transaction, ISet<int> accounts = null)
        {
            if (transaction == null || transaction.Details == null)
            {
                return false;
            }
            decimal debits = 0;
            decimal credits = 0;
            foreach (var item in transaction.Details)
            {
                if (item.IsCredit)
                {
                    credits += item.Amount;
                }
                else
                {
                    debits += item.Amount;
                }
            }
            if (accounts != null)
            {
                foreach (var item in transaction.Details)
                {
                    if (!accounts.Contains(item.AccountId))
                    {
                        return false;
                    }
                }
            }
            return credits == debits;
        }
        /// <summary>
        /// Tạo phần details cho một transaction từ mô tả detzails dưới dạng TransactionItem
        /// </summary>
        /// <param name="details">Mô tả các detail của một transaction</param>
        /// <param name="transaction">Transaction cần thêm phần details</param>
        private static void AddTransactionDetails(DTO.TransactionItem[] details, Transaction transaction)
        {
            foreach (var detail in details)
            {
                if (detail.IsCredit)
                {
                    transaction.TransactionCredits.Add(new TransactionCredit()
                    {
                        AccountId = detail.AccountId,
                        Amount = detail.Amount
                    });
                }
                else
                {
                    transaction.TransactionDebits.Add(new TransactionDebit()
                    {
                        AccountId = detail.AccountId,
                        Amount = detail.Amount
                    });
                }
            }
        }

        public int InsertTransaction(DTO.DTOTransaction tranInfo)
        {
            if (!isTransactionValid(tranInfo))
            {
                return -1;
            }
            using (AccountingDBEntities db = Schema.ModelFactory.getInstance())
            {
                Transaction transaction = new Transaction()
                {
                    Date = tranInfo.Date.Date,
                    Explanation = tranInfo.Explanation
                };
                AddTransactionDetails(tranInfo.Details, transaction);
                db.AddToTransactions(transaction);
                try
                {
                    db.SaveChanges();
                }
                catch (Exception ex)
                {
                    if (Logger.IsErrorEnabled)
                    {
                        Logger.Error("Fail to persist change into DB.", ex);
                    }
                    return -1;
                }
                return transaction.Id;
            }
        }

        public string RemoveTransaction(int Id)
        {
            using (AccountingDBEntities db = Schema.ModelFactory.getInstance())
            {
                var query = from t in db.Transactions
                            where t.Id == Id
                            select t;
                Transaction tran = query.SingleOrDefault();
                if (tran != null)
                {
                    db.DeleteObject(tran);
                }
                else
                {
                    return "Transaction Id not found!";
                }
                try
                {
                    db.SaveChanges();
                }
                catch (Exception ex)
                {
                    if (Logger.IsErrorEnabled)
                    {
                        Logger.Error("Fail to persist change into DB.", ex);
                    }
                    return "Server internal error: fail to persist into DB.";
                }
                return null;
            }
        }

        public string EditTransaction(DTO.DTOTransaction tranInfo)
        {
            if (!isTransactionValid(tranInfo))
            {
                return "Invalid transaction details. Total debits does not equal total credits.";
            }
            using (AccountingDBEntities db = Schema.ModelFactory.getInstance())
            {
                var query = from t in db.Transactions
                            where t.Id == tranInfo.Id
                            select t;
                Transaction tran = query.SingleOrDefault();
                if (tran == null)
                {
                    return "Transaction Id not found!";
                }
                tran.Date = tranInfo.Date.Date;
                tran.Explanation = tranInfo.Explanation;
                if (db.Connection.State != System.Data.ConnectionState.Open)
                {
                    db.Connection.Open();
                }
                //Để đảm bảo tính consistency khi thực hiện chỉnh sửa, tạo một transaction trong DB.
                var dbTransaction = db.Connection.BeginTransaction();
                db.ClearTransaction(tran.Id);
                AddTransactionDetails(tranInfo.Details, tran);
                try
                {
                    db.SaveChanges();
                    dbTransaction.Commit();
                }
                catch (Exception ex)
                {
                    try
                    {
                        dbTransaction.Rollback();
                    }
                    catch (Exception)
                    {
                        if (Logger.IsErrorEnabled)
                        {
                            Logger.Error("Fail to roll back change in transaction.", ex);
                        }
                    }
                    if (Logger.IsErrorEnabled)
                    {
                        Logger.Error("Fail to persist change through Transaction into DB.", ex);
                    }
                    return "Server internal error. Fail to persist into DB.";
                }
                return null ;
            }
        }

        public DTO.DTOTransaction GetTransaction(int id)
        {
            using (AccountingDBEntities db = Schema.ModelFactory.getInstance())
            {
                var query = from t in db.Transactions
                            where t.Id == id
                            select t;
                Transaction tran = query.SingleOrDefault();
                if (tran == null)
                {
                    return null;
                }
                return BuildTransactionInfo(tran);
            }
        }

        public List<DTO.DTOTransaction> GetTransactions(DateTime fromDate, DateTime toDate)
        {
            fromDate = fromDate.Date;
            toDate = toDate.Date;
            List<DTO.DTOTransaction> result = new List<DTO.DTOTransaction>();
            using (AccountingDBEntities db = Schema.ModelFactory.getInstance())
            {
                var query = from t in db.Transactions
                            where t.Date >= fromDate && t.Date <= toDate
                            select t;
                foreach (var tran in query)
                {
                    DTO.DTOTransaction tranInfo = BuildTransactionInfo(tran);
                    result.Add(tranInfo);
                }
            }
            return result;
        }

        /// <summary>
        /// Build a DTO.Transaction that wrap the information of a transaction object in db.
        /// </summary>
        /// <param name="tran"></param>
        /// <returns></returns>
        private static DTO.DTOTransaction BuildTransactionInfo(Transaction tran)
        {
            List<DTO.TransactionItem> details = new List<DTO.TransactionItem>();
            //Danh sách debit nằm trước
            foreach (var d in tran.TransactionDebits)
            {
                details.Add(new DTO.TransactionItem()
                {
                    Id = d.Id,
                    AccountId = d.AccountId,
                    Amount = d.Amount,
                    IsCredit = false
                });
            }
            foreach (var d in tran.TransactionCredits)
            {
                details.Add(new DTO.TransactionItem()
                {
                    Id = d.Id,
                    AccountId = d.AccountId,
                    Amount = d.Amount,
                    IsCredit = true
                });
            }
            DTO.DTOTransaction tranInfo = new DTO.DTOTransaction()
            {
                Id = tran.Id,
                Date = tran.Date,
                Explanation = tran.Explanation,
                Details = details.ToArray()
            };
            return tranInfo;
        }
    }
}